package session

import (
	"encoding/gob"
	"fmt"
	"log"
	"net"
	"reflect"
	"sort"

	"test.com/common"
	"test.com/fusion"
	"test.com/protocol"
)

var InstGameServerMgr *GameServerManager

const (
	GS_READY_STATUS_NONE = iota
	GS_READY_STATUS_FOR_GATE
	GS_READY_STATUS_FOR_GAME
)

type GameSession struct {
	fusion.Session
	gsName       string
	logicServers []uint32
	ReadyStatus  int
}

type GameServerInstance struct {
	*common.GameServerInfo
	gsSession *GameSession
}

type GameServerManager struct {
	gsInstMap map[uint32]*GameServerInstance
}

func NewGameServerManagerInstance() {
	InstGameServerMgr = &GameServerManager{}
}

func (obj *GameSession) InitAndStartSendCoroutine(s *fusion.ServiceBase, conn net.Conn) {
	obj.ReadyStatus = GS_READY_STATUS_NONE
	obj.Session.InitAndStartSendCoroutine(s, conn)
}

func (obj *GameSession) GetServerID() uint32 {
	if len(obj.logicServers) != 0 {
		return obj.logicServers[0]
	}
	return 0
}

func (mgr *GameServerManager) LoadDataFromDB() {
	var gsInfo *common.GameServerInfo
	gsInfoVals, err := fusion.LoadTableFromDB(common.GlobalDB, reflect.TypeOf(gsInfo), "")
	if err != nil {
		log.Fatalf("LoadDataFromDB failed, %s\n", err)
	}
	gsInfos := gsInfoVals.Interface().([]*common.GameServerInfo)
	mgr.gsInstMap = make(map[uint32]*GameServerInstance, len(gsInfos))
	for _, gsInfo := range gsInfos {
		mgr.gsInstMap[gsInfo.ID] = &GameServerInstance{GameServerInfo: gsInfo}
	}
}

func (mgr *GameServerManager) ReloadGameServerInfos(gsInfos []*common.GameServerInfo) {
	var allGsSessions = map[*GameSession]bool{}
	for _, gsInfo := range mgr.gsInstMap {
		if gsInfo.gsSession != nil {
			allGsSessions[gsInfo.gsSession] = true
		}
	}
	var gsInstMap = make(map[uint32]*GameServerInstance, len(gsInfos))
	for _, gsInfo := range gsInfos {
		gsInstMap[gsInfo.ID] = &GameServerInstance{GameServerInfo: gsInfo}
	}
	for gsSession := range allGsSessions {
		gsSession.logicServers = nil
		var gsIP = gsSession.GetIP()
		for _, gsInfo := range gsInstMap {
			if gsInfo.InternalIP == gsIP && gsInfo.InternalName == gsSession.gsName {
				gsInfo.gsSession = gsSession
				gsSession.logicServers = append(gsSession.logicServers, gsInfo.ID)
			}
		}
		if len(gsSession.logicServers) != 0 {
			sort.Slice(gsSession.logicServers, func(i, j int) bool {
				return gsSession.logicServers[i] < gsSession.logicServers[j]
			})
		} else {
			gsSession.Shutdown(false)
		}
	}
	mgr.gsInstMap = gsInstMap
}

func (mgr *GameServerManager) PackAllGameServerInfos(pck *fusion.NetPacket) {
	pck.Write(uint32(len(mgr.gsInstMap)))
	for _, gsInfo := range mgr.gsInstMap {
		gob.NewEncoder(pck.GetWriter()).Encode(gsInfo.GameServerInfo)
		pck.Write(gsInfo.gsSession != nil)
	}
}

func (mgr *GameServerManager) BuildAllGameServerInfosPacket() *fusion.NetPacket {
	pck := &fusion.NetPacket{Opcode: protocol.SLC_PUSH_GAME_SERVER_LIST}
	mgr.PackAllGameServerInfos(pck)
	return pck
}

func (mgr *GameServerManager) UpdateGameServerStatus2LoginServers(session *GameSession, isOnline bool) {
	if len(session.logicServers) != 0 {
		pck := &fusion.NetPacket{Opcode: protocol.SLC_PUSH_GAME_SERVER_STATUS}
		pck.Write(isOnline)
		for _, gsID := range session.logicServers {
			pck.Write(gsID)
		}
		InstLoginServerMgr.SendPacket2LoginServer(0, pck)
	}
}

func (mgr *GameServerManager) RegisterGameServer(session *GameSession, name string) (uint32, error) {
	if name != "" {
		var gsIP = session.GetIP()
		for _, gsInfo := range mgr.gsInstMap {
			if gsInfo.InternalIP == gsIP && gsInfo.InternalName == name {
				if gsInfo.gsSession != nil {
					return 0, fmt.Errorf("RegisterGameServer: already register game server %s[%s]", gsIP, name)
				}
				gsInfo.gsSession = session
				session.logicServers = append(session.logicServers, gsInfo.ID)
			}
		}
		if len(session.logicServers) == 0 {
			return 0, fmt.Errorf("RegisterGameServer: can't find game server %s[%s]", gsIP, name)
		}
		sort.Slice(session.logicServers, func(i, j int) bool {
			return session.logicServers[i] < session.logicServers[j]
		})
		session.gsName = name
		session.ReadyStatus = GS_READY_STATUS_FOR_GAME
	} else {
		session.ReadyStatus = GS_READY_STATUS_FOR_GATE
	}
	log.Printf("register game server `%s`.`%s` status `%d` successfully.\n", session, name, session.ReadyStatus)
	mgr.UpdateGameServerStatus2LoginServers(session, true)
	return session.GetServerID(), nil
}

func (mgr *GameServerManager) UnregisterGameServer(session *GameSession) error {
	log.Printf("unregister game server `%s`.`%s` status `%d`.\n", session, session.gsName, session.ReadyStatus)
	mgr.UpdateGameServerStatus2LoginServers(session, false)
	for _, gsID := range session.logicServers {
		if gsInfo, isOK := mgr.gsInstMap[gsID]; isOK {
			gsInfo.gsSession = nil
		}
	}
	session.logicServers = nil
	session.ReadyStatus = GS_READY_STATUS_NONE
	return nil
}

func (mgr *GameServerManager) SendPacket2GameServer(gsID uint32, pck *fusion.NetPacket) {
	if gsID != 0 {
		if gsInfo, isOK := mgr.gsInstMap[gsID]; isOK {
			if gsInfo.gsSession != nil {
				gsInfo.gsSession.SendPacket(pck)
			}
		}
	} else {
		var allGsSessions = map[*GameSession]bool{}
		for _, gsInfo := range mgr.gsInstMap {
			if gsInfo.gsSession != nil {
				if _, isOK := allGsSessions[gsInfo.gsSession]; !isOK {
					allGsSessions[gsInfo.gsSession] = true
					gsInfo.gsSession.SendPacket(pck)
				}
			}
		}
	}
}

func (mgr *GameServerManager) RPCBlockInvoke2GameServer(gsID uint32, pck *fusion.NetPacket) (*fusion.RPCBlockContext, error) {
	var gsInfo *GameServerInstance
	var isOK bool
	if gsInfo, isOK = mgr.gsInstMap[gsID]; !isOK {
		return nil, fmt.Errorf("RPCBlockInvoke2GameServer: can't find game server `%d`", gsID)
	}
	if gsInfo.gsSession == nil {
		return nil, fmt.Errorf("RPCBlockInvoke2GameServer: game server `%d` is offline", gsID)
	}
	return gsInfo.gsSession.RPCBlockInvoke(pck, fusion.RPCInvokeTimeout)
}
